#include <stdio.h>
#include <stdlib.h>
#include "iffdecode.h"
#include <QPainter>
#include <qmessagebox.h>

/* TODO:
    - Width compensation on interlaced images must double size in X for real
*/

struct BMHDChunk {
        unsigned short Width;
        unsigned short Height;
        unsigned short XOrigin;
        unsigned short YOrigin;
        unsigned char Bitplanes;
        unsigned char Mask;
        unsigned char Compression;
        unsigned char Padding;          // unused
        unsigned short Transparency;
        unsigned short Aspect;
        unsigned short PageWidth;
        unsigned short PageHeight;
} BMHD;

IFFDecode::IFFDecode() {
	error = NULL;	
	is_interlaced = false;
	FilePointer = 0;
	BMHD.Width = 320;
	BMHD.Height = 256;
}

bool IFFDecode::IsInterlaced() {
	return is_interlaced;
}

unsigned short IFFDecode::GetWidth() {
	return BMHD.Width;
}

unsigned short IFFDecode::GetHeight() {
	return BMHD.Height;
}

bool IFFDecode::Error( const char *str ) {
	if( FileBuffer ) free( FileBuffer );
	if( chunky ) free( chunky );
	error = str;
	return( false );
}

unsigned char IFFDecode::ReadByte() {
	char ch = 0;
	ch = (char) FileBuffer[FilePointer];
	FilePointer++;
	return( ch );
}

unsigned short IFFDecode::ReadWord() {
	return( (ReadByte()<<8)|ReadByte() );
}

unsigned int IFFDecode::ReadLong() {
	return( (ReadWord()<<16)|ReadWord() );
}

void IFFDecode::ReadToArray( char *buf, int len ) {
	for( int x=0; x<len; x++ ) {
		buf[x] = ReadByte();
	}
	buf[len] = '\0';
}

void IFFDecode::ReadBMHD() {
//	BMHD.Length = ReadLong();
	BMHD.Width = ReadWord();
	BMHD.Height = ReadWord();
	BMHD.XOrigin = ReadWord();
	BMHD.YOrigin = ReadWord();
	BMHD.Bitplanes = ReadByte();
	BMHD.Mask = ReadByte();
	BMHD.Compression = ReadByte();
	BMHD.Padding = ReadByte();
	BMHD.Transparency = ReadWord();
	BMHD.Aspect = ReadWord();
	BMHD.PageWidth = ReadWord();
	BMHD.PageHeight = ReadWord();

	//printf( "Width=%d, Height=%d\n", BMHD.Width, BMHD.Height );
	//printf( "PageWidth=%d, PageHeight=%d\n", BMHD.PageWidth, BMHD.PageHeight );
	//printf( "Compression=%d (%s)\n", BMHD.Compression, ((BMHD.Compression==1)?"RLE":"None") );
}

void IFFDecode::PlanarToChunky() {
	QPainter p(pixmap);
	for( int y=0; y<BMHD.Height; y++ ) {
		for( int x=0; x<BMHD.Width; x++ ) {
			char c = chunky[x+(y*BMHD.Width)];
//			if( c > 0 ) printf( "c=%d ", c );
			unsigned char r = cmap[(c*3)+0];
			unsigned char g = cmap[(c*3)+1];
			unsigned char b = cmap[(c*3)+2];
			p.setPen( QColor( r, g, b ) );
			p.drawPoint( x, y );
		}
	}
}

void IFFDecode::Poke( unsigned char value ) {
	static int depth=0;
	int d;
	
	QPainter p(pixmap);

/*
	switch( depth ) {
		case 1:
			p.setPen( QPen( Qt::black, 1 ));
			break;
		case 2:
			p.setPen( QPen( Qt::red, 1 ));
			break;
		default:
			p.setPen( QPen( Qt::green, 1 ));
			break;
	}

	if( (value&128) !=0 ) p.drawPoint( (xpos)+0, ypos );
	if( (value&64) !=0 ) p.drawPoint( (xpos)+1, ypos );
	if( (value&32) !=0 ) p.drawPoint( (xpos)+2, ypos );
	if( (value&16) !=0 ) p.drawPoint( (xpos)+3, ypos );
	if( (value&8) !=0 ) p.drawPoint( (xpos)+4, ypos );
	if( (value&4) !=0 ) p.drawPoint( (xpos)+5, ypos );
	if( (value&2) !=0 ) p.drawPoint( (xpos)+6, ypos );
	if( (value&1) !=0 ) p.drawPoint( (xpos)+7, ypos );
	
	p.setPen( QPen( Qt::green, 1 ));
	p.drawPoint( xpos, ypos );
*/

	d = (ypos*BMHD.Width)+(xpos);
	//int pos = (xpos)+(ypos*BMHD.Width);
	chunky[d] |= (((value&128)==0)?0:1<<depth);
	chunky[d+1] |= (((value&64)==0)?0:1<<depth);
	chunky[d+2] |= (((value&32)==0)?0:1<<depth);
	chunky[d+3] |= (((value&16)==0)?0:1<<depth);
	chunky[d+4] |= (((value&8)==0)?0:1<<depth);
	chunky[d+5] |= (((value&4)==0)?0:1<<depth);
	chunky[d+6] |= (((value&2)==0)?0:1<<depth);
	chunky[d+7] |= (((value&1)==0)?0:1<<depth);
	xpos += 8;
	if( xpos >= BMHD.Width ) {
		depth++;
		if( depth >= BMHD.Bitplanes ) {
			depth = 0;
			ypos++;
		}
		xpos = 0;
	}
}

bool IFFDecode::Load( char *filename ) {
	char buf[256];

	FilePointer = 0;
	xpos = 0;
	ypos = 0;
	is_interlaced = false;

	fp = fopen( filename, "rb" );
	if( ! fp ) return Error( "Unable to open input file for reading" );
	
	fseek( fp, 0L, SEEK_END );
	FileSize = ftell( fp );
	fseek( fp, 0L, SEEK_SET );

	FileBuffer = (char *)malloc( FileSize );
	if( FileBuffer == NULL ) return Error( "Unable to allocate memory" );

	int bytes_read = fread( FileBuffer, FileSize, 1, fp );
	fclose( fp );

	ReadToArray( buf, 4 );
	if( strncmp( buf, "FORM", 4 ) != 0 ) return Error( "File format not recognized" );

	int FileLength = ReadLong();
//	printf( "Expected file length: %d bytes\n", FileLength );

	ReadToArray( buf, 4 );
	FileLength -= 4;
	if( strncmp( buf, "ILBM", 4 ) != 0 ) return Error( "File format not recognized" );

	/* Read loop start */

	while( FileLength > 0 ) {
		ReadToArray( buf, 4 );
		FileLength -= 4;
		if( strncmp( buf, "BMHD", 4 ) == 0 ) {
			int bmhdlen = ReadLong();
			ReadBMHD();
			FileLength -= bmhdlen;
			pixmap = new QPixmap( BMHD.Width, BMHD.Height );
			chunky = (char *)malloc( BMHD.Width*BMHD.Height*BMHD.Bitplanes );
			QPainter p(pixmap);
			p.fillRect( pixmap->rect(), Qt::white );
		} else if( strncmp( buf, "CMAP", 4 ) == 0 ) {
			int cmaplen = ReadLong();
			FileLength -= cmaplen;
			cmap = (char *)malloc( cmaplen*3 );
			for( int x=0; x<(cmaplen/3); x++ ) {
				cmap[(x*3)+0] = ReadByte();
				cmap[(x*3)+1] = ReadByte();
				cmap[(x*3)+2] = ReadByte();
			}
		} else if( strncmp( buf, "DPI ", 4 ) == 0 ) {
			int dpilen = ReadLong();
			FileLength -= dpilen;
			ReadToArray( buf, dpilen );
		} else if( strncmp( buf, "GRAB", 4 ) == 0 ) {
			int grablen = ReadLong();
			FileLength -= grablen;
			ReadToArray( buf, grablen );
		} else if( strncmp( buf, "CAMG", 4 ) == 0 ) {
			int camglen = ReadLong();
			FileLength -= camglen;
			int camgdata = ReadLong();
			if( camgdata & 4 )
				is_interlaced = true;
		} else if( strncmp( buf, "DPPS", 4 ) == 0 ) {
			int dppslen = ReadLong();	
			FileLength -= dppslen;
			ReadToArray( buf, dppslen );
		} else if( strncmp( buf, "CRNG", 4 ) == 0 ) {
			int crnglen = ReadLong();
			FileLength -= crnglen;
			ReadToArray( buf, crnglen );
		} else if( strncmp( buf, "ANNO", 4 ) == 0 ) {
			int annolen = ReadLong();
			FileLength -= annolen;
			ReadToArray( buf, annolen );
		} else if( strncmp( buf, "BODY", 4 ) == 0 ) {
			int bodylen = ReadLong();
			int bytes_wanted = ((BMHD.Width>>3)*BMHD.Height)*BMHD.Bitplanes;
			int type=0;

			while( bytes_wanted > 0 ) {
//				printf( "Bytes remaining: %d\n", bytes_wanted );
				char f = ReadByte();
				if( (f>=0) && (f<=127) ) {
					type=1;
					/* copy the next n+1 bytes literally */
					for( int n=0; n<=f; n++ ) {
						Poke( ReadByte() );
						bytes_wanted--;
					}
				} else if( (f<=-1) && (f>=-127) ) {
					type=2;
					/* replicate the next byte -n+1 times */
					unsigned char in = ReadByte();
					for( int n=0; n<=-f; n++ ) {
						Poke( in );
						bytes_wanted--;
					}
				} else {
					// This shouldn't happen, encoder error.
					QMessageBox::critical( 0, "qtiff",
					QString("An error occoured during decoding the IFF contents") );
					Error( "Error" );
					break;
				}
			}
			PlanarToChunky();
			return Error( "No error" );
		} else {
			printf( "Buf was: '%s'\n", buf );
			return Error( "No known chunk found" );
		}
	}
	free( FileBuffer );
	free( chunky );
	return( true );
}

